/**
 * 
 */
package smallChanges.Tokenization;

import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import net.sourceforge.pmd.ast.ASTCompilationUnit;
import net.sourceforge.pmd.ast.JavaCharStream;
import net.sourceforge.pmd.ast.JavaParserTokenManager;
import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.ast.SimpleJavaNode;
import net.sourceforge.pmd.ast.SimpleNode;
import net.sourceforge.pmd.ast.Token;
import smallChanges.statements.MultiTokenCodeRange2D;
import smallChanges.statements.ParseUtilities;
import smallChanges.util.SCConstants;

/**
 * @author jspacco
 *
 */
public class TokenSet implements Iterable<SCToken>
{
    private TreeSet<SCToken> set=new TreeSet<SCToken>();
    
    public static TokenSet readTokenSet(Reader reader, String sourceVersion)
    throws IOException
    {
        // null fileID is OK if we're strictly working with files
        // and not the DB
        return readTokenSet(reader, sourceVersion, null, null);
    }
    
    public static TokenSet readTokenSet(
            Reader reader, 
            String sourceVersion, 
            Long fileID,
            Long transID)
    throws IOException
    {
    	JavaParserTokenManager manager=new JavaParserTokenManager(new JavaCharStream(reader));
        
        TokenSet result=new TokenSet();
        
        while (true) {
            Token token=manager.getNextToken();
            if (token.kind==0) break;
            // We're ignoring all brackets
            if (token.image.equals("{") || token.image.equals("}"))
                continue;
            
            SCToken sct=new SCToken(token, fileID, transID);
            result.add(sct);
        }
        return result;
    }
    
    public TokenSet() { }
    
    private TokenSet(SortedSet<SCToken> set) {
        if (set instanceof TreeSet) {
            this.set=(TreeSet<SCToken>)set;
        } else {
            set=new TreeSet<SCToken>();
            set.addAll(set);
        }
    }
    
    public static TokenSet readTokenSet(String filename, String sourceVersion)
    throws IOException
    {
        FileReader rdr=new FileReader(filename);
    	TokenSet tokenSet=readTokenSet(rdr, sourceVersion);
    	rdr.close();
    	return tokenSet;
    }
    
    
    
    
    
    public static TokenSet readTokenSet(InputStream is, String sourceVersion)
    throws IOException
    {
        InputStreamReader rdr=new InputStreamReader(is);
    	TokenSet tokenSet=readTokenSet(rdr, sourceVersion);
    	rdr.close();
    	return tokenSet;
    }
    
    public void add(SCToken token) {
        set.add(token);
    }
    
    public void addAll(Collection<SCToken> collection) {
    	set.addAll(collection);
    }
    
    public void addAll(TokenSet tokenSet) {
        for (SCToken t : tokenSet) {
            add(t);
        }
    }
    
    public String getImage() {
    	StringBuffer buf=new StringBuffer();
    	for (SCToken s : set) {
    		buf.append(s.getImage()+" ");
    	}
    	//buf.replace(buf.length()-2, buf.length()-1, "");
    	buf.replace(buf.length()-1, buf.length(), "");
    	return buf.toString();
    }
    
    public String toString() {
    	StringBuffer buf=new StringBuffer();
    	for (SCToken s : set) {
    		buf.append(s+" ");
    	}
    	//buf.replace(buf.length()-1, buf.length(), "");
    	return buf.toString();
    }
    
   
    
    public SCToken getLast() {
        return set.last();
    }
    
    public SCToken getFirst() {
        return set.first();
    }
    
    public TokenSet getAllTokensBetweenLines(int beginLine, int endLine)
    {
    	SCToken start=new SCToken(beginLine, 0, beginLine, 0);
        
        SCToken end=new SCToken(endLine, Integer.MAX_VALUE, endLine, Integer.MAX_VALUE);
        
        return getAllTokensInRange(start, end);
    }
    
    public TokenSet getAllTokensInRange(int s0BeginLine,
            int s0BeginColumn,
            int s1EndLine,
            int s1EndColumn)
    {
        // start token has begin and end lines and columns the same
        SCToken start=new SCToken(s0BeginLine, s0BeginColumn,s0BeginLine,s0BeginColumn);
        
        // end token also has begin and end lines and columns the same
        SCToken end=new SCToken(s1EndLine,s1EndColumn,s1EndLine,s1EndColumn);
        
        return getAllTokensInRange(start, end);
    }
    
    public TokenSet getAllTokensInRange(SCToken start, SCToken end)
    {
        // XXX Hack alert
        // we want an inclusive range of elements, but keeping
        // in mind that the that the "end" token we're passed
        // may be a synthetic token that spans an existing token
        // there's a method that returns this but
        // it's java 1.6 and won't run on my Mac with 1.5!
        TokenSet result = inclusiveSubSet(start, end);
        
        return result;
    }
    
    /**
     * Necessary for Java 1.5 compatibility
     * My laptop still doesn't have Java 1.6!
     * @param start
     * @param end
     * @return
     */
    private TokenSet inclusiveSubSet(SCToken start, SCToken end)
    {
        TokenSet result=new TokenSet();
        SortedSet<SCToken> s=set.tailSet(end);
        if (s.size()>0) {
            SCToken t=s.first();
            // Only add this token if it ends exactly where the "end"
            // token we were given ends (the token we were passed might
            // by synthetic).  For example, we may wan the tokens in
            // the range (5, 5, 1, 1) to (8, 8, 10, 10) (i.e. all
            // tokens from line 5, col 1 through line 8, col 10)
            // We would add token (8, 8, 3, 10) but NOT (8, 8, 11, 13)
            // The first case is a token that ends at col 10, the second
            // is a token that actually ends after our given range
            // but may still be the FIRST token after the range we
            // were looking for because there may not BE a token at
            // col 10 (there may just be whitespace).
            //
            // All this goes away once my Mac can run OS 10.5!
            if (t.getEndLine() <= end.getEndLine() && t.getEndColumn() <= end.getEndColumn()) {
                result.add(t);
            }
        }
        result.addAll(set.subSet(start, end));
        return result;
    }
    
    public Iterator<SCToken> iterator() {
        return set.iterator();
    }

    /**
     * @param args
     */
    public static void main(String[] args)
    throws Exception
    {
    	String filename="test-data/statement-maps/File01.java";
    	
    	FileReader rdr=new FileReader(filename);
        String sourceVersion="1.5";
        Long fileID=0L;
        Long transID=0L;
   
        JavaParserTokenManager manager=new JavaParserTokenManager(new JavaCharStream(rdr));
        
        TokenSet tm=new TokenSet();
        
        while (true) {
            Token token=manager.getNextToken();
            if (token.kind==0) break;
            
            SCToken sct=new SCToken(token, fileID, transID);
            tm.add(sct);
        }
        
        for (CodeRange2D t : tm) {
            System.out.println(t);
        }
        
        final ASTCompilationUnit cu = ParseUtilities.parseToASTCompilationUnit(
        		new FileReader(filename),
        		sourceVersion);
        
        JavaParserVisitorAdapter visitor=new JavaParserVisitorAdapter() {

			@Override
			public Object visit(SimpleJavaNode node, Object data)
			{
				System.out.println(node.getClass()+": "+
						"("+node.getBeginLine()+", "+node.getBeginColumn()+") "+
						" => "+"("+node.getEndLine()+", "+node.getEndColumn()+") ");
				return super.visit(node, data);
			}
        	
			
        };
        
        cu.childrenAccept(visitor, null);
        
        
        System.out.println(tm.getAllTokensInRange(1,12,1,12));
        
        System.out.println(tm.getAllTokensInRange(1,1,1,11));
        
        System.out.println("Checking method: ");
        System.out.println(tm.getAllTokensBetweenLines(2, 5));
    }

    public int size() {
        return set.size();
    }

	public TokenSet getAllTokensComprisingNode(SimpleNode node)
	{
		return getAllTokensInRange(node.getBeginLine(),
				node.getBeginColumn(),
				node.getEndLine(),
				node.getEndColumn());
	}
    public TokenSet getPrecedingTokens(SimpleJavaNode node)
    {
        // note that we can't use a synthetic token here, it doesn't work
        // correctly with the comparator
        
        TokenSet subset=getAllTokensComprisingNode(node);
        if (subset.size()>0)
            return getPrecedingTokens(getAllTokensComprisingNode(node).getFirst());
        // If we've gotten here, then we're dealing with an empty "node", such as
        // an empty else block, like this:
        // 
        // if (x) {
        //      foo();
        // } else {
        //
        // }
        // 
        // We'll use a fake token and hope it works!
        return getPrecedingTokens(new SCToken(node.getBeginLine(), 
                node.getBeginColumn(),
                node.getEndLine(),
                node.getEndColumn()));
    }
    
    public boolean isPrecedingTokenImageInSet(Set<String> set, SimpleJavaNode node)
    {
        SCToken token=getOnePrecedingToken(node);
        if (set.contains(token.getImage())) {
            return true;
        }
        return false;
    }
    
    public boolean isPrecedingTokenImageInSet(Set<String> set, SCToken t)
    {
        SCToken token=getOnePrecedingToken(t);
        if (set.contains(token.getImage())) {
            return true;
        }
        return false;
    }
    
    public SCToken getOnePrecedingToken(SimpleJavaNode node)
    {
        return getPrecedingTokens(node).getLast();
    }
    
    public SCToken getOnePrecedingToken(SCToken token)
    {
        return getPrecedingTokens(token).getLast();
    }
    
    public SCToken getOneFollowingToken(SimpleJavaNode node)
    {
        return getFollowingTokens(node).getFirst();
    }
    
    public SCToken getOneFollowingToken(SCToken token)
    {
        return getFollowingTokens(token).getFirst();
    }
    
    public TokenSet getPrecedingTokens(int beginLine, int beginColumn, int endLine, int endColumn)
    {
        // don't use synthetic tokens, it doesn't work correctly with
        // the comparator
        return getPrecedingTokens(
                getAllTokensInRange(beginLine, beginColumn, endLine, endColumn).
                getFirst());
    }
    
    public TokenSet getPrecedingTokens(SCToken token)
    {
        return new TokenSet(set.headSet(token));
    }
    
    public TokenSet getFollowingTokens(SimpleJavaNode node)
    {
        return getFollowingTokens(node.getBeginLine(), node.getBeginColumn(), 
                node.getEndLine(), node.getEndColumn());
    }
    
    public TokenSet getFollowingTokens(int beginLine, int beginColumn, int endLine, int endColumn)
    {
        SCToken token=new SCToken(endLine, endColumn, endLine, endColumn);
        // XXX should be using inclusiveSubSet() methods?
        //return new TokenSet(set.tailSet(token));
        return strictlyGreaterTailSet(token);
    }
    
    public TokenSet getFollowingTokens(SCToken token)
    {
        return strictlyGreaterTailSet(token);
    }
    
    private TokenSet strictlyGreaterTailSet(SCToken token)
    {
        /*
         * This is very, very ugly.  I want all of the elements that are strictly
         * greater than the given token, taking into account that the ending line/col
         * of the given token may "span" an actual multi-character token, for example:
         * 
         * Consider the range of the if condition, and its coordinates:
         * 
         * if (x>y)
         * (11, 13) (11, 16)
         * 
         * We're going to create a "synthetic" token from (11, 16) to (11, 16).
         * Because of the way the comparators work for SCToken, the first character
         * after the synthatic token ending at (11, 16) is actually the token 'y'!  
         * But we want the right paren, so we need to use an iterator to get the next 
         * token, then perform the tailset operation again.  
         * 
         * Now, compare with this example and its coordinates:
         * 
         * if (x>10)
         * (11, 13) (11, 17)
         *
         * We're going to be looking for the first token after line 11, col 17, which is
         * the right-paren we're looking for, because the token '10' spans two characters
         * and so doesn't fix the coordinates of the synthetic token from (11, 17) to (11, 17)
         *
         * This is all rather hideous, but luckily it works.
         * 
         */
        SortedSet<SCToken> subset=set.tailSet(token);
        SCToken first=subset.first();
        
        if (first.getEndLine() <= token.getEndLine() && 
                first.getEndColumn() <= token.getEndColumn())
        {
            // we need to get the second element of the collection
            Iterator<SCToken> ii=subset.iterator();
            // XXX Assert that there are at least 2 tokens here
            ii.next();
            if (ii.hasNext())
                token=ii.next();
            return new TokenSet(set.tailSet(token));
        }
        return new TokenSet(subset);
    }

    public boolean contains(SCConstants token)
    {
        return set.contains(token);
    }

    public void merge(MultiTokenCodeRange2D range)
    {
        addAll(range.getTokensInRange());
    }
}
